//css_dbg /t:exe, /args:{00020813-0000-0000-c000-000000000046} excell;
using System;
using System.IO;
using System.Text;
using Microsoft.Win32;
using System.Reflection;
using System.Windows.Forms;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Diagnostics;
using CSScriptLibrary;
using csscript;
 
class Scripting
{
    class Script
    {
        static bool activeX = false;
        [STAThread]
        static public void Main(string[] args)
        {
            //Debug.Assert(false);
            const string usage = "Usage: cscscript com <<file1> <file2> [/r] [/ax]> | <<file1> /u> | <<name>|<guid> <file2> [/ax]>...\n" +
                                "Converterts type library of the COM Server to assembly.\n" +
                                "<file1> - the name of the COM Server file\n" +
                                "<file2> - the name of the assembly file (.dll is a default extension)\n" +
                                "<name> - VersionIndependentProgID or ProgID of the COM Server\n" +
                                "<guid> - GUID of the COM Server ({XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}).\n" +
                                "/ax - treats the COM library as an ActiveX COM and generate Windows Forms Control assembly\n" +
                                "/r - registers COM dll before importing\n" +
                                "/u - unregisters COM dll\n";

            try
            {
                //System.Diagnostics.Debug.Assert(false);

                if (args.Length < 2)
                {
                    Console.WriteLine(usage);
                    return;
                }

                string input = "";
                string output = "";
                bool register = false, unregister = false;
                foreach (string arg in args)
                {
                    if (arg.ToLower() == "/u")
                        unregister = true;
                    else if (arg.ToLower() == "/r")
                        register = true;
                    else if (arg.ToLower() == "/ax")
                        Script.activeX = true;
                    else if (input == "")
                        input = arg;
                    else if (output == "")
                        output = arg;
                }

                string ide = Environment.GetEnvironmentVariable("CSScriptDebugging");

                if (!File.Exists(input))
                {
                    if (File.Exists(Path.Combine(Path.GetDirectoryName(EntryScript), input)))
                        input = Path.Combine(Path.GetDirectoryName(EntryScript), input);
                }
                else
                    input = Path.GetFullPath(input);

                if (unregister)
                {
                    new DllRegServer(input).UnRegister();
                }
                else
                {
                    //MessageBox.Show(input);//for testing

                    if (register)
                    {
                        new DllRegServer(input).Register();
                        ImportTlbFromFile(input, ResolveAsmLocation(output));
                    }
                    else
                    {
                        if (File.Exists(input) || input.ToLower().EndsWith(".dll"))
                            ImportTlbFromFile(input, ResolveAsmLocation(output));
                        else if (input.StartsWith("{"))
                            ImportTlbFromGUID(input, ResolveAsmLocation(output));
                        else
                            ImportTlbFromName(input, ResolveAsmLocation(output));
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw ex;
            }
        }

        static string EntryScript
        {
            get
            {
                if (Environment.GetEnvironmentVariable("EntryScript") != null) //standalone execution
                    return Environment.GetEnvironmentVariable("EntryScript");
				else if (CSSEnvironment.PrimaryScriptFile != null) //hosted execution 
                    return CSSEnvironment.PrimaryScriptFile;
                else
                    throw new Exception("Error: Cannot obtain the entry script path");
            }
        }
        static string ResolveAsmLocation(string file)
        {
            //System.Diagnostics.Debug.Assert(false);
            if (CSScript.GlobalSettings.HideAutoGeneratedFiles == Settings.HideOptions.HideAll)
                return Path.Combine(CSSEnvironment.GetCacheDirectory(EntryScript), Path.GetFileName(file));
            else
                return Path.Combine(Path.GetDirectoryName(EntryScript), file);
        }
        static void ImportTlbFromFile(string file, string asmName)
        {
            string com = Path.GetFullPath(file);
            string asm = Path.GetFullPath(asmName.EndsWith(".dll") ? asmName : asmName + ".dll");

            //MessageBox.Show(asm); //for testing

            if (!File.Exists(file))
                throw new FileNotFoundException("The file cannot be found", file);

            FileInfo comInfo = new FileInfo(com);
            if (File.Exists(asm))
            {
                if (new FileInfo(asm).CreationTimeUtc != comInfo.CreationTimeUtc)
                    File.Delete(asm);
                else
                    return;
            }

            RunApp(GetTlbImpApp(), "\"" + com + "\" \"/out:" + asm + "\"");

            if (!File.Exists(asm))
                throw new Exception("Specified file cannot be imported.");

            new FileInfo(asm).CreationTimeUtc = comInfo.CreationTimeUtc;
        }
        static void ImportTlbFromName(string name, string asmName)
        {
            bool versionIndependant = (name.Split(".".ToCharArray()).Length == 2);
            bool anyVersion = name.EndsWith(".*");
            if (anyVersion)
                name = name.Replace(".*", "");

            object progIDValue = null;

            using (RegistryKey CLSIDKey = Registry.ClassesRoot.OpenSubKey(@"CLSID"))
                foreach (string guid in CLSIDKey.GetSubKeyNames())
                    using (RegistryKey progIDKey = CLSIDKey.OpenSubKey(guid + "\\" + (versionIndependant ? "VersionIndependentProgID" : "ProgID")))
                        if (progIDKey != null && (progIDValue = progIDKey.GetValue("")) != null)
                            if (anyVersion ? progIDKey.GetValue("").ToString().ToUpper().StartsWith(name.ToUpper()) : progIDKey.GetValue("").ToString().ToUpper() == name.ToUpper())
                            {
                                ImportTlbFromFile(CLSIDKey.OpenSubKey(guid + "\\InprocServer32").GetValue("").ToString(), asmName);
                                return;
                            }

            if (versionIndependant)
                ImportTlbFromName(name + ".*", asmName); //if not found as version independent, try to find any version 
            else
                throw new Exception("Cannot find COM object " + name);
        }
        static void ImportTlbFromGUID(string guid, string asmName)
        {
            //System.Diagnostics.Debug.Assert(false);
            using (RegistryKey guidKey = Registry.ClassesRoot.OpenSubKey(@"CLSID\\" + guid))
            {
                if (guidKey != null)
                {
                    ImportTlbFromFile(guidKey.OpenSubKey("InprocServer32").GetValue("").ToString(), asmName);
                    return;
                }
                else
                {
                    using (RegistryKey typeLibKey = Registry.ClassesRoot.OpenSubKey(@"TypeLib\" + guid))
                    {
                        if (typeLibKey != null)
                        {
                            string[] maxVer = new string[] { "1", "0" };

                            foreach (string subKey in typeLibKey.GetSubKeyNames())
                            {
                                string[] ver = subKey.Split('.');
                                if (Convert.ToInt32(ver[0]) > Convert.ToInt32(maxVer[0]))
                                {
                                    maxVer = ver;
                                }
                                else if (Convert.ToInt32(ver[1]) > Convert.ToInt32(maxVer[1]))
                                {
                                    maxVer = ver;
                                }

                            }

                            using (RegistryKey majorVesruionKey = typeLibKey.OpenSubKey(maxVer[0] + "." + maxVer[1]))
                            {
                                int maxMinorVer = 0;
                                int minorVer = 0;

                                foreach (string subKey in majorVesruionKey.GetSubKeyNames())
                                    if (int.TryParse(subKey, out minorVer))
                                        if (maxMinorVer < minorVer)
                                            maxMinorVer = minorVer;

                                string majorVesruion = maxVer[0] + "." + maxVer[1];

                                ImportTlbFromFile(typeLibKey.OpenSubKey(majorVesruion + "\\" + maxMinorVer + "\\win32").GetValue("").ToString(), asmName);
                                return;
                            }
                        }
                    }
                }
            }
            throw new Exception("Cannot find COM object " + guid);
        }
        static string GetTlbImpApp()
        {
            string appName = Script.activeX ? "AxImp.exe" : "TlbImp.exe";

            //In the future it needs to be replaced with the automatic search for the TlbImp.exe
            string[] version = Environment.Version.ToString().Split(".".ToCharArray());
            if (version[0] == "4")
                return Environment.ExpandEnvironmentVariables(@"%CSSCRIPT_DIR%\lib\tools\v4.0\" + appName);
            if (version[0] == "2")
                return Environment.ExpandEnvironmentVariables(@"%CSSCRIPT_DIR%\lib\tools\v2.0\" + appName);
            if (version[0] == "1" && version[1] == "1")
                return Environment.ExpandEnvironmentVariables(@"%CSSCRIPT_DIR%\lib\tools\v1.1\" + appName);
            else
                return "Location of the " + appName + " is unknown.";
        }
        static string RunApp(string app, string args)
        {
            Process myProcess = new Process();
            myProcess.StartInfo.FileName = app;
            myProcess.StartInfo.Arguments = args;
            myProcess.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
            myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.RedirectStandardOutput = true;
            myProcess.StartInfo.CreateNoWindow = true;

            myProcess.Start();

            StringBuilder sb = new StringBuilder();
            string line = null;
            while (null != (line = myProcess.StandardOutput.ReadLine()))
            {
                sb.Append(line);
                sb.Append("\r\n");
            }
            myProcess.WaitForExit();
            return sb.ToString();
        }
    }

    /// <summary>
    /// This class was kindly provided by Mattias Sjogren (http://www.msjogren.net/dotnet/eng/samples/dotnet_dynpinvoke.asp)
    /// </summary>
    public class DllRegServer
    {
        bool IsWin64Process()
        {
            return "".GetType().Assembly.Location.IndexOf("Framework64") != -1;
        }
        private string m_sDllFile;
        private static ModuleBuilder s_mb;
        private Type m_tDllReg;

        public DllRegServer(string dllFile)
        {
            if (IsWin64Process())
            {
                //should run System32\regsvr32.exe or/and SysWOW64/regsvr32.exe as an external process
                //as COM server dll may by of x32 and cannot be loaded in to current x63 process memory for
                //invoking DllRegisterServer/DllUnregisterServer

                MessageBox.Show("COM.cs cannot do COM Servr registrtions on x64.");
                throw new Exception("COM.cs cannot do COM Servr registrtions on x64.");
            }

            m_sDllFile = Path.GetFullPath(dllFile);
            CreateDllRegType();
        }
        public void Register()
        {
            InternalRegServer(false);
        }
        public void UnRegister()
        {
            InternalRegServer(true);
        }
        private void InternalRegServer(bool fUnreg)
        {
            string sMemberName = fUnreg ? "DllUnregisterServer" : "DllRegisterServer";

            int hr = (int)m_tDllReg.InvokeMember(sMemberName, BindingFlags.InvokeMethod, null,
                                                  Activator.CreateInstance(m_tDllReg), null);
            if (hr != 0)
                Marshal.ThrowExceptionForHR(hr);
        }
        private void CreateDllRegType()
        {
            if (s_mb == null)
            {
                // Create dynamic assembly    
                AssemblyName an = new AssemblyName();
                an.Name = "DllRegServerAssembly" + Guid.NewGuid().ToString("N");
                AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);

                // Add module to assembly
                s_mb = ab.DefineDynamicModule("DllRegServerModule");
            }

            // Add class to module
            TypeBuilder tb = s_mb.DefineType("DllRegServerClass" + Guid.NewGuid().ToString("N"));

            MethodBuilder meb;

            // Add PInvoke methods to class
            meb = tb.DefinePInvokeMethod("DllRegisterServer", m_sDllFile,
              MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl,
              CallingConventions.Standard, typeof(int), null, CallingConvention.StdCall, CharSet.Auto);

            // Apply preservesig metadata attribute so we can handle return HRESULT ourselves
            meb.SetImplementationFlags(MethodImplAttributes.PreserveSig | meb.GetMethodImplementationFlags());

            meb = tb.DefinePInvokeMethod("DllUnregisterServer", m_sDllFile,
              MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl,
              CallingConventions.Standard, typeof(int), null, CallingConvention.StdCall, CharSet.Auto);

            // Apply preservesig metadata attribute so we can handle return HRESULT ourselves
            meb.SetImplementationFlags(MethodImplAttributes.PreserveSig | meb.GetMethodImplementationFlags());

            // Create the type
            m_tDllReg = tb.CreateType();
        }
    }
}